package net.posick.ws.http;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import net.posick.ws.Constants;

import org.apache.http.HttpException;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.protocol.SyncBasicHttpContext;

import android.content.Context;
import android.util.Log;

public class HTTPServer implements Runnable, Closeable
{
    public static final String LOG_TAG = Constants.LOG_TAG + "." + HTTPServer.class.getSimpleName();

    public static int DEFAULT_SERVER_PORT = 8080;
    
    protected ThreadGroup threadGroup = new ThreadGroup(LOG_TAG + " threads");
    
    protected Executor threadPool = Executors.newCachedThreadPool(new ThreadFactory()
    {
        @Override
        public Thread newThread(Runnable r)
        {
            return new Thread(r, LOG_TAG + " thread");
        }
    });
    
    protected boolean running = false;
    
    protected int serverPort = DEFAULT_SERVER_PORT;
    
    protected Context context = null;
    
    protected BasicHttpProcessor httpProcessor = null;
    
    protected SyncBasicHttpContext parentHttpContext = null;
    
    protected HttpService httpService = null;
    
    protected HttpRequestHandlerRegistry registry = null;
    
    protected ServerSocket serverSocket;
    
    
    public HTTPServer(Context context)
    {
        this.setContext(context);
        
        httpProcessor = new BasicHttpProcessor();
        parentHttpContext = new SyncBasicHttpContext(new BasicHttpContext());
        parentHttpContext.setAttribute(Constants.ANDROID_CONTEXT, context);
        
        httpProcessor.addInterceptor(new ResponseDate());
        httpProcessor.addInterceptor(new ResponseServer());
        httpProcessor.addInterceptor(new ResponseContent());
        httpProcessor.addInterceptor(new ResponseConnControl());
        
        httpService = new HttpService(httpProcessor, new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory());
        
        registry = new HttpRequestHandlerRegistry();
        httpService.setHandlerResolver(registry);
    }
    
    
    public void setContext(Context context)
    {
        this.context = context;
    }
    
    
    public Context getContext()
    {
        return context;
    }
    
    
    public HttpRequestHandler lookup(String pattern)
    {
        if (running)
        {
            return registry.lookup(pattern);
        } else
        {
            return null;
        }
    }
    
    
    public String register(String pattern, HttpRequestHandler handler)
    throws HttpException
    {
        if (running)
        {
            registry.register(pattern, handler);
            InetAddress address = serverSocket.getInetAddress();
            int port = serverSocket.getLocalPort();
            return "http://" + address.getHostAddress() + ":" + port + (pattern.startsWith("/") ? pattern : "/" + pattern);
        } else
        {
            throw new HttpException("HTTP Server is not running, cannot register HttpRequestHandler.");
        }
    }
    
    
    public void unregister(String pattern)
    throws HttpException
    {
        if (running)
        {
            registry.unregister(pattern);
        } else
        {
            throw new HttpException("HTTP Server is not running, cannot unregister HttpRequestHandler.");
        }
    }
    
    
    public void run()
    {
        try
        {
            Log.i(LOG_TAG, "Starting HTTP Server");
            
            running = true;
            
            this.serverSocket = new ServerSocket(serverPort);
            
            final ServerSocket serverSocket = this.serverSocket;
            serverSocket.setReuseAddress(true);
            
            while (running)
            {
                try
                {
                    Log.i(LOG_TAG, this.toString() + " is waiting for connection on " + serverSocket.getLocalSocketAddress());
                    final Socket socket = serverSocket.accept();
                    threadPool.execute(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            try
                            {
                                DefaultHttpServerConnection serverConnection = new DefaultHttpServerConnection();
                                serverConnection.bind(socket, new BasicHttpParams());
                                httpService.handleRequest(serverConnection, new BasicHttpContext(parentHttpContext));
                                serverConnection.shutdown();
                            } catch (Exception e)
                            {
                                Log.e(LOG_TAG, e.getMessage(), e);
                            }
                        }
                    });
                } catch (Exception e)
                {
                    Log.e(LOG_TAG, e.getMessage(), e);
                }
            }
        } catch (IOException e)
        {
            Log.e(LOG_TAG, "Error starting HTTP Server on port " + serverPort + " - " + e.getMessage(), e);
        }
        
        running = false;
    }
    
    
    public boolean isRunning()
    {
        return running;
    }
    
    
    public void start()
    {
        running = true;
        threadPool.execute(this);
    }
    
    
    public void close()
    throws IOException
    {
        running = false;
        
        if (serverSocket != null)
        {
            try
            {
                serverSocket.close();
                serverSocket = null;
            } catch (Exception e)
            {
                Log.w(LOG_TAG, "Exception thrown closing Server Socket for port " + serverPort + " - " + e.getMessage());
            }
        }
    }
}